using System;
using System.Collections.Generic;
using System.Configuration;

namespace SubversionReports.Entities
{
    public class Repository : List<Revision>, IPageable
    {
        #region fields

        private int id;
        private string url;
        private string description;
        private int nonPagedCount;

        private SortableDictionary<string, string> authorIcons;
        private SortableDictionary<string, int> authorCommits;
        private SortableDictionary<string, int> authorChanges;

        private SortableDictionary<string, List<Tag>> taggedPaths;

        #endregion

        #region accessors

        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>The id.</value>
        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        /// <summary>
        /// Gets or sets the URL.
        /// </summary>
        /// <value>The URL.</value>
        public string Url
        {
            get { return url; }
            set { url = value; }
        }

        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        /// <value>The description.</value>
        public string Description
        {
            get { return description; }
            set { description = value; }
        }

        /// <summary>
        /// Gets or sets the non paged count.
        /// </summary>
        /// <value>The non paged count.</value>
        public int NonPagedCount
        {
            get
            {
                if (nonPagedCount < Count)
                    nonPagedCount = Count;
                return nonPagedCount;
            }
            set { nonPagedCount = value; }
        }

        /// <summary>
        /// Gets or sets the author icons.
        /// </summary>
        /// <value>The author icons.</value>
        public SortableDictionary<string, string> AuthorIcons
        {
            get
            {
                if (authorIcons == null)
                {
                    authorIcons = new SortableDictionary<string, string>();
                    int avatarId = 201;
                    ForEach(delegate(Revision revision) { if (!authorIcons.ContainsKey(revision.Author)) authorIcons.Add(revision.Author, string.Format("http://subversionreports.googlecode.com/svn/trunk/Avatars/avatar{0}.png", ++avatarId)); } );
                }
                return authorIcons;
            }
        }

        /// <summary>
        /// Gets the highest revision.
        /// </summary>
        /// <value>The highest revision.</value>
        public int HighestRevision
        {
            get
            {
                int highestRevision = 0;
                ForEach(delegate(Revision revision) { if (revision.Id > highestRevision) highestRevision = revision.Id; });
                return highestRevision;
            }
        }

        /// <summary>
        /// Gets the lowest revision.
        /// </summary>
        /// <value>The lowest revision.</value>
        public int LowestRevision
        {
            get
            {
                int lowestRevision = -1;
                ForEach(delegate(Revision revision) { if (lowestRevision.Equals(-1) || revision.Id < lowestRevision) lowestRevision = revision.Id; });
                return lowestRevision;
            }
        }

        /// <summary>
        /// Gets the author commits.
        /// </summary>
        /// <value>The author commits.</value>
        public SortableDictionary<string, int> AuthorCommits
        {
            get
            {
                if (authorCommits == null || authorChanges == null)
                {
                    authorCommits = new SortableDictionary<string, int>();
                    authorChanges = new SortableDictionary<string, int>();
                    ForEach(delegate(Revision revision)
                                {
                                    if (!authorCommits.ContainsKey(revision.Author))
                                        authorCommits.Add(revision.Author, 0);
                                    authorCommits[revision.Author]++;
                                    if (!authorChanges.ContainsKey(revision.Author))
                                        authorChanges.Add(revision.Author, 0);
                                    authorChanges[revision.Author] += revision.Paths.Count;
                                });
                }
                return authorCommits;
            }
        }

        /// <summary>
        /// Gets the author changes.
        /// </summary>
        /// <value>The author changes.</value>
        public SortableDictionary<string, int> AuthorChanges
        {
            get
            {
                if (authorCommits == null || authorChanges == null)
                {
                    authorCommits = new SortableDictionary<string, int>();
                    authorChanges = new SortableDictionary<string, int>();
                    ForEach(delegate(Revision revision)
                                {
                                    if (!authorCommits.ContainsKey(revision.Author))
                                        authorCommits.Add(revision.Author, 0);
                                    authorCommits[revision.Author]++;
                                    if (!authorChanges.ContainsKey(revision.Author))
                                        authorChanges.Add(revision.Author, 0);
                                    authorChanges[revision.Author] += revision.Paths.Count;
                                });
                }
                return authorChanges;
            }
        }

        /// <summary>
        /// Gets the tagged paths.
        /// </summary>
        /// <value>The tagged paths.</value>
        public SortableDictionary<string, List<Tag>> TaggedPaths
        {
            get
            {
                if (taggedPaths == null)
                {
                    string tagRootPath = ConfigurationManager.AppSettings["DefaultRepositoryTagDirectory"] ?? "/";
                    taggedPaths = new SortableDictionary<string, List<Tag>>();
                    ForEach(delegate (Revision revision)
                                {
                                    revision.Paths.ForEach(delegate (Path path)
                                                               {
                                                                   if (path.CopyFromRevision.HasValue
                                                                       && !0.Equals((int)path.CopyFromRevision)
                                                                       && path.RevisionPath.StartsWith(tagRootPath))
                                                                   {
                                                                       Tag tag = new Tag();
                                                                       tag.Author = revision.Author;
                                                                       tag.Comment = revision.Comment;
                                                                       tag.Date = revision.Date;
                                                                       tag.Name = path.RevisionPath;
                                                                       tag.EndRevision = (int)path.CopyFromRevision;
                                                                       tag.Path = path.CopyFromPath;
                                                                       if (!taggedPaths.ContainsKey(tag.Path))
                                                                           taggedPaths.Add(tag.Path, new List<Tag>());
                                                                       taggedPaths[tag.Path].Add(tag);
                                                                   }
                                                               });
                                });
                    taggedPaths.Sort(delegate(KeyValuePair<string, List<Tag>> tp1, KeyValuePair<string, List<Tag>> tp2) { return tp1.Key.CompareTo(tp2.Key); });
                    List<string> uselessTagPaths = new List<string>();
                    
                    taggedPaths.ForEach(delegate (KeyValuePair<string, List<Tag>> taggedPath)
                                            {
                                                if (taggedPath.Value.Count < 2)
                                                    uselessTagPaths.Add(taggedPath.Key);
                                                else
                                                {
                                                    taggedPath.Value.Sort(delegate(Tag t1, Tag t2) { return t1.EndRevision.CompareTo(t2.EndRevision); });
                                                    for (int i = 0; i < taggedPath.Value.Count; i++)
                                                        taggedPath.Value[i].StartRevision = i.Equals(0) ? 1 : taggedPath.Value[i - 1].EndRevision + 1;
                                                }
                                            });
                    uselessTagPaths.ForEach(delegate (string uselessTagPath) { taggedPaths.Remove(uselessTagPath); });
                }
                return taggedPaths;
            }
        }

        /// <summary>
        /// Gets the page.
        /// </summary>
        /// <param name="pageNumber">The page number.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="sortBy">The sort by.</param>
        /// <param name="sortDirection">The sort direction.</param>
        /// <returns></returns>
        public Repository GetPage(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
        {
            switch(sortBy)
            {
                case "Id":
                case "Date":
                    Sort(delegate(Revision r1, Revision r2) { return sortDirection.Equals(SortDirection.Ascending) ? r1.Id.CompareTo(r2.Id) : r2.Id.CompareTo(r1.Id); });
                    break;
                case "Author":
                    Sort(delegate(Revision r1, Revision r2) { return sortDirection.Equals(SortDirection.Ascending) ? r1.Author.CompareTo(r2.Author) : r2.Author.CompareTo(r1.Author); });
                    break;
            }
            int firstIndex = ((pageNumber - 1) * pageSize);
            int size = Math.Min(Count - firstIndex, pageSize);
            Repository pagedRepository = new Repository();
            pagedRepository.nonPagedCount = Count;
            pagedRepository.Id = Id;
            pagedRepository.Description = Description;
            pagedRepository.Url = Url;
            GetRange(firstIndex, size).ForEach(delegate(Revision revision) { pagedRepository.Add(revision); });
            return pagedRepository;
        }

        #endregion

        #region public methods

        /// <summary>
        /// Gets a filtered repository where property {propertyName} has a value of {value} if {filterType} is set to {include} 
        /// or property {propertyName} does not have a value of {value} if {filterType} is set to {exclude}.
        /// </summary>
        /// <param name="revisionPropertyName">Name of the revision property.</param>
        /// <param name="value">The value.</param>
        /// <param name="filterType">Type of the filter.</param>
        /// <returns>A subset of the parent repository filtered by the specified parameters.</returns>
        public Repository Filter(string revisionPropertyName, object value, FilterType filterType)
        {
            return Filter(revisionPropertyName, value, value, filterType);
        }

        /// <summary>
        /// Gets a filtered repository where property {propertyName} has a value between {minValue} and {maxValue} if {filterType} is set to {include} 
        /// or property {propertyName} does not have a value between {minValue} and {maxValue} if {filterType} is set to {exclude}.
        /// </summary>
        /// <param name="revisionPropertyName">Name of the revision property.</param>
        /// <param name="minValue">The min value.</param>
        /// <param name="maxValue">The max value.</param>
        /// <param name="filterType">Type of the filter.</param>
        /// <returns>A subset of the parent repository filtered by the specified parameters.</returns>
        public Repository Filter(string revisionPropertyName, object minValue, object maxValue, FilterType filterType)
        {
            Repository filteredRepository = new Repository();
            switch (revisionPropertyName)
            {
                case "Author":
                    if (filterType.Equals(FilterType.Include))
                        ForEach(delegate(Revision revision) { if (revision.Author.Equals(minValue)) filteredRepository.Add(revision); });
                    else
                        ForEach(delegate(Revision revision) { if (!revision.Author.Equals(minValue)) filteredRepository.Add(revision); });
                    break;
                case "Id":
                    if (filterType.Equals(FilterType.Include))
                        ForEach(delegate(Revision revision) { if (revision.Id >= (int)minValue && revision.Id <= (int)maxValue) filteredRepository.Add(revision); });
                    else
                        ForEach(delegate(Revision revision) { if (!(revision.Id >= (int)minValue && revision.Id <= (int)maxValue)) filteredRepository.Add(revision); });
                    break;
                case "Date":
                    if (filterType.Equals(FilterType.Include))
                        ForEach(delegate(Revision revision) { if (revision.Date >= (DateTime)minValue && revision.Date <= (DateTime)maxValue) filteredRepository.Add(revision); });
                    else
                        ForEach(delegate(Revision revision) { if (!(revision.Date >= (DateTime)minValue && revision.Date <= (DateTime)maxValue)) filteredRepository.Add(revision); });
                    break;
                case "Path":
                    if (filterType.Equals(FilterType.Include))
                        ForEach(delegate(Revision revision) { if (revision.Paths.Find(delegate(Path path) { return path.RevisionPath.Equals(minValue); }) != null) filteredRepository.Add(revision); });
                    else
                        ForEach(delegate(Revision revision) { if (revision.Paths.Find(delegate(Path path) { return path.RevisionPath.Equals(minValue); }) == null) filteredRepository.Add(revision); });
                    break;
                case "PathChildren":
                    if (filterType.Equals(FilterType.Include))
                        ForEach(delegate(Revision revision) { if (revision.Paths.Find(delegate(Path path) { return path.RevisionType.Equals(PathRevisionType.Added) && IsDirectDescendant((string)minValue, path.RevisionPath); }) != null) if (!filteredRepository.Contains(revision)) filteredRepository.Add(revision); });
                    else
                        ForEach(delegate(Revision revision) { if (revision.Paths.Find(delegate(Path path) { return path.RevisionType.Equals(PathRevisionType.Added) && IsDirectDescendant((string)minValue, path.RevisionPath); }) == null) if (!filteredRepository.Contains(revision)) filteredRepository.Add(revision); });
                    break;
                default:
                    throw new ArgumentOutOfRangeException("revisionPropertyName", revisionPropertyName, "revisionPropertyName must be one of: Id, Author, Date.");
            }
            filteredRepository.NonPagedCount = filteredRepository.Count;
            filteredRepository.Id = Id;
            filteredRepository.Description = Description;
            filteredRepository.Url = Url;
            return filteredRepository;
        }

        #endregion

        #region private methods

        /// <summary>
        /// Determines whether [is direct descendant] [the specified parent].
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="child">The child.</param>
        /// <returns>
        /// 	<c>true</c> if [is direct descendant] [the specified parent]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsDirectDescendant(string parent, string child)
        {
            if (parent.Equals("/") && !child.Equals("/") && !child.Substring(1).Contains("/"))
                return true;
            if (!child.Equals(parent) && child.StartsWith(parent + "/"))
                return child.Split('/').Length.Equals(parent.Split('/').Length + 1);
            return false;
        }

        #endregion

        #region constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Revision"/> class.
        /// </summary>
        public Repository () { }

        #endregion
    }

    public enum FilterType
    {
        Include, Exclude
    }

    public enum SortDirection
    {
        Ascending, Descending
    }
}
